package org.chainsale.prefuse.graph;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JCheckBox;
import javax.swing.JPanel;

import org.chainsale.prefuse.action.VisabilityActivity;
import org.chainsale.prefuse.action.ZoomDistortion;
import org.chainsale.prefuse.action.ZoomToFitAction;
import org.chainsale.prefuse.controls.FlyInOutZoomControl;
import org.chainsale.prefuse.layout.MyForcedDirectedLayout;
import org.chainsale.prefuse.layout.MyLayout;
import org.chainsale.prefuse.renderer.MyEdgeRenderer;

import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.Action;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.distortion.Distortion;
import prefuse.action.distortion.FisheyeDistortion;
import prefuse.action.filter.GraphDistanceFilter;
import prefuse.activity.Activity;
import prefuse.controls.AnchorUpdateControl;
import prefuse.controls.DragControl;
import prefuse.controls.FocusControl;
import prefuse.controls.NeighborHighlightControl;
import prefuse.controls.PanControl;
import prefuse.controls.WheelZoomControl;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Edge;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Schema;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.GraphicsLib;
import prefuse.util.PrefuseLib;
import prefuse.util.display.DisplayLib;
import prefuse.util.force.DragForce;
import prefuse.util.force.ForceSimulator;
import prefuse.util.force.NBodyForce;
import prefuse.util.force.SpringForce;
import prefuse.visual.EdgeItem;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.InGroupPredicate;

public class FuseGraphPanel extends JPanel {
	
	private static final long serialVersionUID = 7406086639133276902L;
	
	/*************************	graph	***************************/
	public static final String GRAPH = "graph";
	public static final String GRAPH_NODES = "graph.nodes";
	public static final String GRAPH_EDGES = "graph.edges";
	
	/*************************	nodes	***************************/
	public static final String NODE_NAME = "name";
	public static final String NODE_ALIAS = "alias";
	public static final String NODE_TYPE = "type";
	public static final String NODE_IMAGE = "image";
	public static final String NODE_MASS_VALUE = "massValue";
	public static final String NODE_MARK = "mark";
	public static final String NODE_CHECKED = "checked";
	public static final String NODE_INBOUND = "inBound";
	
	/*************************	edges	***************************/
	public static final String EDGE_DECORATORS = "edgeDeco";
	public static final String EDGE_SOURCE = Graph.DEFAULT_SOURCE_KEY;
	public static final String EDGE_TARGET = Graph.DEFAULT_TARGET_KEY;
	public static final String EDGE_REFTYPE = "refType";
	public static final String EDGE_ALIAS = NODE_ALIAS;
	public static final String EDGE_SPRING_COE = "springCoefficient";
	public static final String EDGE_SPRING_LEN = "springLength";
	
	/*************************	actions	***************************/
	public static final String ACTION_DRAW = "draw";
	public static final String ACTION_LAYOUT = "layout";
	public static final String ACTION_ZOOM_TO_FIT = "zoomToFit";
	public static final String ACTION_ALL_VISABLE = "allVisable";
	public static final String ACTION_DISTORT_ZOOM = "distortZoom";
	public static final String ACTION_DISTORT_FEYE = "distortfEye";
	public static final String ACTION_UPDATE_GRAPH = "updateGraph";
	
	private static final Schema DECORATOR_SCHEMA = PrefuseLib.getVisualItemSchema();
	
	private Visualization visualization;
	private Graph graph;
	private Display display;
	private GraphDistanceFilter distanceFilter;
	private MyForcedDirectedLayout layout;
	private AnchorUpdateControl zoomDistortControl;

	private Map<String, Node> nodeMap = new HashMap<String, Node>();
	private Map<String, Edge> edgeMap = new HashMap<String, Edge>();// <src.row_tar.row, >
	
	private Table nodes = null;
	private Table edges = null;
	
	static {
		DECORATOR_SCHEMA.setDefault(VisualItem.INTERACTIVE, true);
		DECORATOR_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.blue(128));
	}

	static int i = 1;
	public FuseGraphPanel() {
		
		super(new BorderLayout());
		
		nodes = setupNodes();
		edges = setupEdges();
		graph = new Graph(nodes, edges, true);
		visualization = new Visualization();
		
		init();
	}

	private void init() {
		LabelRenderer labelRenderer = new LabelRenderer(NODE_NAME);
		labelRenderer.setImageField(NODE_IMAGE);
		labelRenderer.setImagePosition(4);
		labelRenderer.setRoundedCorner(8, 8);

		EdgeRenderer edgeRenderer = new MyEdgeRenderer(1, 1);
		edgeRenderer.setArrowHeadSize(4, 12);

		DefaultRendererFactory rf = new DefaultRendererFactory(labelRenderer, edgeRenderer);
		rf.add(new InGroupPredicate(EDGE_DECORATORS), new LabelRenderer(EDGE_REFTYPE));

		visualization.setRendererFactory(rf);

		setGraph(graph, NODE_NAME);

		// fix selected focus nodes
        visualization.getGroup(Visualization.FOCUS_ITEMS).addTupleSetListener(new TupleSetListener() {
        	public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem) {
            	if (add.length == 1 && (add[0] instanceof EdgeItem)) {
            		return;
            	}
            	
                for (int i=0; i<rem.length; ++i ) {
                    VisualItem vi = (VisualItem)rem[i];
                	if (vi.getRow() >= 0) {
                		vi.setFixed(false);
                	}
                }
                for (int i=0; i<add.length; ++i ) {
                	VisualItem vi = (VisualItem)add[i];
                	vi.setFixed(false);
                	vi.setFixed(true);
                }
                visualization.run(ACTION_DRAW);
            }
        });
		
		visualization.addDecorators(EDGE_DECORATORS, GRAPH_EDGES, DECORATOR_SCHEMA);
		visualization.setValue(EDGE_DECORATORS, null, VisualItem.INTERACTIVE, Boolean.FALSE);

		VisabilityActivity visable = new VisabilityActivity();
		visualization.putAction(ACTION_ALL_VISABLE, visable);

		distanceFilter = new GraphDistanceFilter(GRAPH, 30);
		distanceFilter.setEnabled(false);

		ColorAction fill = new ColorAction(GRAPH_NODES, VisualItem.FILLCOLOR, ColorLib.rgb(200, 200, 255));
		fill.add(VisualItem.FIXED, ColorLib.rgb(255, 100, 100));
		fill.add(VisualItem.HIGHLIGHT, ColorLib.rgb(255, 200, 125));
		fill.add("ingroup('" + Visualization.SEARCH_ITEMS + "')", ColorLib.rgb(255, 190, 190));

		ActionList draw = new ActionList();
		draw.add(distanceFilter);
		draw.add(fill);
		draw.add(new ColorAction(GRAPH_NODES, VisualItem.STROKECOLOR, 0));
		draw.add(new ColorAction(GRAPH_NODES, VisualItem.TEXTCOLOR, ColorLib.rgb(0, 0, 0)));
		draw.add(new ColorAction(GRAPH_EDGES, VisualItem.FILLCOLOR, ColorLib.gray(200)));
		draw.add(new ColorAction(GRAPH_EDGES, VisualItem.STROKECOLOR, ColorLib.gray(200)));
		draw.add(new ColorAction(EDGE_DECORATORS, VisualItem.TEXTCOLOR, ColorLib.rgb(200, 200, 200)));

		ActionList animate = new ActionList(Activity.INFINITY);
		
		ForceSimulator fsim = new ForceSimulator();
		fsim.addForce(new NBodyForce());
		fsim.addForce(new SpringForce(1.0E-005F, 150.0F));
		fsim.addForce(new DragForce());

		layout = new MyForcedDirectedLayout(GRAPH, fsim);
		
		animate.add(new MyLayout(EDGE_DECORATORS));
		animate.add(layout);
		animate.add(fill);

		animate.add(new RepaintAction());

		visualization.putAction(ACTION_DRAW, draw);
		visualization.putAction(ACTION_LAYOUT, animate);

		visualization.runAfter(ACTION_DRAW, ACTION_LAYOUT);

		visualization.putAction(ACTION_ZOOM_TO_FIT, new ZoomToFitAction());

		display = new Display(visualization);
		display.setSize(720, 500);

		display.addControlListener(new DragControl());
		display.addControlListener(new PanControl());
		display.addControlListener(new ZoomControl());

		display.addControlListener(new ZoomToFitControl());
		display.addControlListener(new FocusControl(1));
		display.addControlListener(new WheelZoomControl());
		display.addControlListener(new NeighborHighlightControl());
		display.addControlListener(new FlyInOutZoomControl());

		display.setForeground(Color.GRAY);

		Color bgColor = new Color(69, 69, 69);
		display.setBackground(bgColor);

		{
	    	ActionList distort = new ActionList();
	    	Distortion mag = new ZoomDistortion();
	    	mag.setGroup(GRAPH_NODES);
	    	distort.add(mag);
	    	//distort.add(new RepaintAction());
	
	    	visualization.putAction(ACTION_DISTORT_ZOOM, distort);
	    	zoomDistortControl = new AnchorUpdateControl(mag, ACTION_DISTORT_ZOOM);
		}
		
		{
	    	ActionList distort = new ActionList();
	    	Distortion feye = new FisheyeDistortion(3, 3);
	    	feye.setGroup(GRAPH_NODES);
	    	distort.add(feye);
	    	distort.add(new RepaintAction());
	
	    	visualization.putAction(ACTION_DISTORT_FEYE, distort);
//	    	fisheyeDistortControl = new AnchorUpdateControl(feye, ACTION_DISTORT_FEYE);
		}
        // update the distortion anchor position to be the current
        // location of the mouse pointer
        
        // now we run our action list
		visualization.run(ACTION_DRAW);
		
		final JCheckBox animation = new JCheckBox("��̬��ʾ", true);
	    animation.setBackground(Color.WHITE);
	    animation.addActionListener(new ActionListener() {
		
			public void actionPerformed(ActionEvent e) {
				animate(animation.isSelected());
			}
	    	
	    });
		
        add(display);
	}
	
	public Visualization getVisualization() {
		return visualization;
	}
	
	public void setEnforceBounds(boolean value) {
    	layout.setEnforceBounds(value);
    }
	
	public void clearNodes() {
		nodeMap.clear();
	}
	
	public Edge addEdge(Node source, Node target) {
		return graph.addEdge(source, target);
	}
	
	public Node getNode(String tAlias, Long templateId) {
		if (nodeMap.containsKey(tAlias)) {
			return nodeMap.get(tAlias);
		} else {
			// System.out.println("Add Template Node: " + tAlias);
			
			Node node = graph.addNode();
			node.set(NODE_NAME, tAlias);
			node.set(NODE_ALIAS, tAlias);
			node.set(NODE_TYPE, tAlias);
//			node.set(NODE_IMAGE, OneConnection.getIconPath(templateId));
			
			nodeMap.put(tAlias, node);
			
			return node;
		}
	}
	
//	public Node getNode(CiBean ci, Long templateId) {
//		if (nodeMap.containsKey(ci.getAlias())) {
//			return nodeMap.get(ci.getAlias());
//		} else {
//			
//			// System.out.println("Add Ci Node: " + ci);
//			
//			Node node = graph.addNode();
//			String displayName = ci.getDisplayName();
//			if (OrdinUtil.strIsNull(displayName)) {
//				displayName = "[" +  ci.getAlias() + "]";
//			} else {
//				if (displayName.length() > 20) {
//					displayName = displayName.substring(0, 20) + "...";
//				}
//			}
//			node.set(NODE_NAME, displayName);
//			node.set(NODE_ALIAS, ci.getAlias());
//			node.set(NODE_TYPE, ci.getDerivedFrom());
//			node.set(NODE_IMAGE, OneConnection.getIconPath(templateId));
//			
//			nodeMap.put(ci.getAlias(), node);
//			
//			return node;
//		}
//	}
	
	public Edge getEdge(Node src, Node tar) {
		String key = src.getRow() + "_" + tar.getRow();
		Edge edge = edgeMap.get(key);
		
		if (null == edge) {
			edge = graph.addEdge(src, tar);
			edgeMap.put(key, edge);
		}
		
		
		return edge;
	}
	
//	public Edge getEdge(Node src, CiBean eci, Node tar) {
//		String key = src.getRow() + "_" + tar.getRow();
//		Edge edge = edgeMap.get(key);
//		
//		if (null == edge) {
//			// System.out.println("Add Edge: " + src.getString(NODE_ALIAS) + "-->" + tar.getString(NODE_ALIAS));
//			
//			edge = graph.addEdge(src, tar);
//			edge.setString(EDGE_ALIAS, eci.getAlias());
//			edge.setString(EDGE_REFTYPE, eci.getDerivedFrom());
//			edgeMap.put(key, edge);
//		}
//		
//		
//		return edge;
//	}
	
	public void magnify(boolean enable) {
    	Display d = visualization.getDisplay(0);
    	if (enable) {
    		//animate(false);
    		d.addControlListener(zoomDistortControl);
    	} else {
    		d.removeControlListener(zoomDistortControl);
    		//animate(true);
    	}
    }
	
	public void redrawAndZoomToFit() {
	    visualization.run(ACTION_DRAW);
	    
	    new Thread(new Runnable() {
	    	public void run() {
	    		try {
					Thread.sleep(300);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
	    		zoomToFit();
	    	}
	    }).start();
	}
	
	public void animate(boolean enable) {
		Action a = visualization.getAction(ACTION_LAYOUT);
		if (a != null) {
			a.setEnabled(enable);
		}
		if (!enable) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public void zoomToFit() {
        Rectangle2D bounds = visualization.getBounds(Visualization.ALL_ITEMS);
        GraphicsLib.expand(bounds, 50 + (int)(1/display.getScale()));
        DisplayLib.fitViewToBounds(display, bounds, 2000);
	}
	
	public void zoomToFit2() {
        Rectangle2D bounds = visualization.getBounds(Visualization.ALL_ITEMS);
        GraphicsLib.expand(bounds, 100 + (int)(1/display.getScale()));
        DisplayLib.fitViewToBounds(display, bounds, 2000);
	}
	
	private Table setupNodes() {
		Table nodes = new Table();
		nodes.addColumn(NODE_NAME, String.class);
		nodes.addColumn(NODE_TYPE, String.class);
		nodes.addColumn(NODE_ALIAS, String.class);
		nodes.addColumn(NODE_IMAGE, String.class);
		nodes.addColumn(NODE_MASS_VALUE, Double.TYPE);
		nodes.addColumn(NODE_MARK, Boolean.TYPE);
		nodes.addColumn(NODE_CHECKED, Boolean.TYPE);
		nodes.addColumn(NODE_INBOUND, Boolean.TYPE);

		return nodes;
	}

	private Table setupEdges() {
		Table edges = new Table();
		edges.addColumn(EDGE_SOURCE, Integer.TYPE);
		edges.addColumn(EDGE_TARGET, Integer.TYPE);
		edges.addColumn(EDGE_REFTYPE, String.class);
		edges.addColumn(EDGE_ALIAS, String.class);
		edges.addColumn(EDGE_SPRING_COE, Double.TYPE);
		edges.addColumn(EDGE_SPRING_LEN, Double.TYPE);

		return edges;
	}

	public void setFocus(Node n) {
//		// System.out.println("setFocus.n=" + n);
		TupleSet ts = visualization.getFocusGroup(Visualization.FOCUS_ITEMS);
		ts.clear();
		VisualItem item = visualization.getVisualItem(GRAPH_NODES, n);
//		// System.out.println("setFocus.item=" + item);
		if (item != null) {
			ts.setTuple(item);
		}
	}

	public void setGraph(Graph g, String label) {
		DefaultRendererFactory drf = (DefaultRendererFactory) visualization.getRendererFactory();
		((LabelRenderer) drf.getDefaultRenderer()).setTextField(label);

		visualization.removeGroup(GRAPH);
		visualization.addGraph(GRAPH, g);
		visualization.setValue(GRAPH_EDGES, null, VisualItem.INTERACTIVE, Boolean.FALSE);
	}

	public void setEnableDistanceFilter(boolean enable) {
		distanceFilter.setEnabled(enable);
		if (!(enable)) {
			visualization.run(ACTION_ALL_VISABLE);
		}
		visualization.run(ACTION_DRAW);
	}

	public void setDistance(int intValue) {
		distanceFilter.setDistance(intValue);
		visualization.run(ACTION_DRAW);
	}

	public TupleSet getGroup(String group) {
		return visualization.getGroup(group);
	}
	
	public Table getEdges() {
		return edges;
	}
	
	public Table getNodes() {
		return nodes;
	}
	
	public Edge getEdge(int row) {
		return graph.getEdge(row);
	}
	
	public void removeEdge(Edge e) {
		// System.out.println("\t\t Remove Edge: " + e.getSourceNode().getString(NODE_ALIAS) + " --> " + e.getTargetNode().getString(NODE_ALIAS));
		
		graph.removeEdge(e);
	}
	
	public Node getNode(int row) {
		return graph.getNode(row);
	}
	
	public void removeNode(Node n) {
		graph.removeNode(n);
	}
	
	public void remove(String alias) {
		nodeMap.remove(alias);
	}
	
//	public void removeNode(CiBean ci) {
//		Node n = getNode(ci, null);
//		// System.out.println("\t\t Remove Ci Node " + ci + ", " + n.getRow());
//		removeNode(n);
//		
//		nodeMap.remove(ci.getAlias());
//		// System.out.println("\t\tnodeMap.get(" + ci.getAlias() + ") = " + nodeMap.get(ci.getAlias()));
//		
////		Iterator<String> iter = nodeMap.keySet().iterator();
////		while (iter.hasNext()) {
////			if (iter.next().equals(ci.getAlias())) {
////				iter.remove();
////			}
////		}
//		
//	}
//	
//	public void removeEdge(CiBean ci) {
//		Node n = getNode(ci, null);
//		
//		Iterator<String> iter = edgeMap.keySet().iterator();
//		
//		String key = null;
//		while (iter.hasNext()) {
//			key = iter.next();
//			if (key.startsWith(n.getRow() + "_") || key.endsWith("_" + n.getRow())) {
//				Edge e = edgeMap.get(key);
//				removeEdge(e);
//				
////				// System.out.println("  ------------------- key=" + key);
//				iter.remove();
//			}
//		}
//		
//	}
	
}